<?xml version="1.0"?> 
<!--
    ExcepGen Build: Ant build script generator
    This file is part of ExcepGen.
    Copyright (c) 2008, 2009, Iparelan Solutions, LLC. All rights reserved.

    ExcepGen is free software: you can redistribute it and/or modify it under
    the terms of the GNU Lesser General Public License as published by the
    Free Software Foundation, either version 3 of the License, or (at your
    option) any later version.

    ExcepGen is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
    more details.

    You should have received a copy of the GNU Lesser General Public License
    along with ExcepGen. If not, see <http://www.gnu.org/licenses/>.

    author: Greg Mattes
    version: July 2008
 -->

<!--
    This transformation is executed by the script "meta-build.xml" in this
    directory.

    This stylesheet transforms a build script written in a simple build
    language specialized for the ExcepGen build into a master Ant build
    file. Currently, the only example of a build script written in the simple
    build language is in the components.xml file in this directory. There is
    no schema written for the this simple build language yet.

    The master build script generated by this stylesheet transformation fits
    the Mediator pattern from the "Gang of Four" Design Patterns book. The
    master build script mediates between individual component build
    scripts. In particular, the master build script manages component
    inter-dependencies to ensure that appropriate component build targets are
    executed.
 -->
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xalan="http://xml.apache.org/xslt"
    exclude-result-prefixes="xalan">

    <!--
        To understand indentation issues see:

        http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5064280

        According the bug report, there is another work-around involving the
        bootclasspath. That option has not been explored, maybe it obviates
        the need to insert this directive?
    -->
    <xsl:output method="xml" indent="yes" xalan:indent-amount="4"/>

    <!--
        Generates the Ant project document root.
    -->
    <xsl:template match="build">
        <xsl:comment>
            <xsl:text>
    ExcepGen Build: generated Ant build script 
    This file is part of ExcepGen.
    Copyright (c) 2008, 2009, Iparelan Solutions, LLC. All rights reserved.

    ExcepGen is free software: you can redistribute it and/or modify it under
    the terms of the GNU Lesser General Public License as published by the
    Free Software Foundation, either version 3 of the License, or (at your
    option) any later version.

    ExcepGen is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
    more details.

    You should have received a copy of the GNU Lesser General Public License
    along with ExcepGen. If not, see &lt;http://www.gnu.org/licenses/>.
</xsl:text> <!-- DO NOT INDENT THIS LINE: it generates a newline without indenting -->
        </xsl:comment>
        <project name="master-build-script" default="all">
            <property name="common.build.script" location="common.xml"/>
            <property name="scm.dir" location="."/>

            <property name="root-directory" location=".."/>

            <property name="external-package-cache.dir" location="${{root-directory}}/External/package-cache"/>
            <mkdir dir="${{external-package-cache.dir}}"/>

            <property file="services.properties"/>

            <xsl:apply-templates select="component" mode="inter-dependencies"/>

            <xsl:apply-templates select="start"/>

            <xsl:call-template name="all-components-global-target">
                <xsl:with-param name="target" select="'doc'"/>
            </xsl:call-template>

            <xsl:call-template name="all-components-global-target">
                <xsl:with-param name="target" select="'test'"/>
            </xsl:call-template>

            <xsl:call-template name="all-components-global-target">
                <xsl:with-param name="target" select="'doc-test'"/>
            </xsl:call-template>

            <xsl:call-template name="all-components-global-target">
                <xsl:with-param name="target" select="'clean'"/>
            </xsl:call-template>
        </project>
    </xsl:template>

    <!--
        Generates all global targets for all components. Global targets contain
        global interdependencies that allow the master build script to have full
        knowledge of the dependency DAG.

        There is a special global target generated that contains logic for
        determining when the build artifacts for a component should be erased to
        obtain a fresh component build. See the templates
        "dependency-clean-check" and "one-dependency-clean-check."

        Properties for the file system location of the component and its primary
        build artifact (a jar archive) are also generated.
    -->
    <xsl:template match="component" mode="inter-dependencies">
        <property name="component.{@name}.dir" location="${{root-directory}}{@location}"/>
        <property name="component.{@name}.jar" location="${{component.{@name}.dir}}/lib/{@name}.jar"/>

        <xsl:call-template name="global-target">
            <xsl:with-param name="target"    select="'jar'"/>
            <xsl:with-param name="component" select="@name"/>
            <xsl:with-param name="depends"   select="@depends"/>
        </xsl:call-template>

        <xsl:call-template name="global-target">
            <xsl:with-param name="target"    select="'run'"/>
            <xsl:with-param name="component" select="@name"/>
            <xsl:with-param name="depends"   select="@name"/>
        </xsl:call-template>

        <xsl:call-template name="global-target">
            <xsl:with-param name="target"    select="'doc'"/>
            <xsl:with-param name="component" select="@name"/>
            <xsl:with-param name="depends"   select="@name"/>
        </xsl:call-template>

        <xsl:call-template name="global-target">
            <xsl:with-param name="target"    select="'test'"/>
            <xsl:with-param name="component" select="@name"/>
            <xsl:with-param name="depends"   select="@name"/>
        </xsl:call-template>

        <xsl:call-template name="global-target">
            <xsl:with-param name="target"    select="'doc-test'"/>
            <xsl:with-param name="component" select="@name"/>
            <xsl:with-param name="depends"   select="@name"/>
        </xsl:call-template>

        <xsl:call-template name="global-target">
            <xsl:with-param name="target"    select="'clean'"/>
            <xsl:with-param name="component" select="@name"/>
            <xsl:with-param name="depends"   select="concat(@name, '.clean.required.check')"/>
            <xsl:with-param name="condition" select="concat(@name, '.clean.required')"/>
        </xsl:call-template>

        <!--
           A component is cleaned when any of the following are true:

           1. Any component on which the component depends is modified.

           2. The build script for the component is modified.

           3. The common build script on which all component builds are based is
              modified.
        -->
        <target name="{@name}.clean.required.check">
            <condition property="{@name}.clean.required">
                <not>
                    <and>
                        <xsl:call-template name="dependency-clean-check">
                            <xsl:with-param name="name"    select="@name"/>
                            <xsl:with-param name="depends" select="@depends"/>
                        </xsl:call-template>

                        <uptodate targetfile="${{component.{@name}.jar}}" srcfile="${{component.{@name}.dir}}/build.xml"/>

                        <uptodate targetfile="${{component.{@name}.jar}}" srcfile="${{common.build.script}}"/>
                    </and>
                </not>
            </condition>
        </target>
    </xsl:template>

    <!--
        Generates one component global target (public, in the generated master
        build script) that calls a component local target (private, in the
        individual component build script).

        Each target has at most three attributes:

        1. The target name

        2. The target dependencies

        3. A conditional expression that decides whether the target is executed.

        The first and second attributes are computed specially for the 'jar'
        target because the 'jar' target is considered to be the "main" target
        for each component. The name of the target that produces the jar is
        simply the name of the component. This is in contrast to other targets
        that append a specific suffix such as the 'doc' target that has the form
        "COMPONENT-doc."

        The dependencies of the jar target are modified to include a final
        dependency on a 'clean' target. This clean target computes whether the
        build products of a component must be erased to get a fresh build of the
        component.
    -->
    <xsl:template name="global-target">
        <xsl:param name="target"/>
        <xsl:param name="component"/>
        <xsl:param name="depends"/>
        <xsl:param name="condition"/>

        <target>
            <xsl:attribute name="name">
                <xsl:choose>
                    <!-- the jar target is special, the component name by itself builds a jar -->
                    <xsl:when test="$target = 'jar'">
                        <xsl:value-of select="$component"/>
                    </xsl:when>
                    <!-- most targets are of the form COMPONENT-TARGET -->
                    <xsl:otherwise>
                        <xsl:value-of select="concat($component, '-', $target)"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>

            <!--
                A component depends on other components and its own clean target to
                ensure it is up-to-date. All of these dependencies are contained in
                a comma separated list.

                No comma is necessary before the clean target in the "depends"
                attribute for a component that does not depend on other components
                because in such a case the clean target is the entire list.
            -->
            <xsl:attribute name="depends">
                <xsl:choose>
                    <!-- the jar target is special, the clean dependency must be appended -->
                    <xsl:when test="$target = 'jar'">
                        <xsl:choose>
                            <!-- no component dependencies, the clean target is the entire list -->
                            <xsl:when test="string-length(normalize-space($depends)) = 0">
                                <xsl:value-of select="concat($component, '-clean')"/>
                            </xsl:when>
                            <!-- the clean target is preceded by component dependencies, a comma is needed for separation -->
                            <xsl:otherwise>
                                <xsl:value-of select="concat($depends, ', ', $component, '-clean')"/>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:when>
                    <!-- non-jar targets simply use the dependencies provided by the caller unmodified -->
                    <xsl:otherwise>
                        <xsl:value-of select="$depends"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>

            <!--
                Optional conditional.
            -->
            <xsl:if test="string-length(normalize-space($condition)) != 0">
                <xsl:attribute name="if">
                    <xsl:value-of select="$condition"/>
                </xsl:attribute>
            </xsl:if>

            <!--
                The body (tasks) of the target.
            -->
            <ant dir="${{component.{$component}.dir}}" target="{$target}"/>
        </target>
    </xsl:template>

    <!--
        Iterates (recursively) over the comma separated dependency list creating
        conditional logic for clean check.
    -->
    <xsl:template name="dependency-clean-check">
        <xsl:param name="name"/>
        <xsl:param name="depends"/>
        <xsl:choose>
            <xsl:when test="contains($depends, ',')"> <!-- list has length at least 2 -->
                <!-- process head of list -->
                <xsl:call-template name="one-dependency-clean-check">
                    <xsl:with-param name="name"           select="$name"/>
                    <xsl:with-param name="one-dependency" select="normalize-space(substring-before($depends, ','))"/>
                </xsl:call-template>
                <!-- recurse on rest of list -->
                <xsl:call-template name="dependency-clean-check">
                    <xsl:with-param name="name"    select="$name"/>
                    <xsl:with-param name="depends" select="substring-after($depends, ',')"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:when test="string-length(normalize-space($depends)) = 0">
                <!-- Do nothing when only spaces or the empty string remain. -->
                <!-- recursion ends -->
            </xsl:when>
            <xsl:otherwise>
                <!-- list has length 1 -->
                <!-- recursion ends -->
                <xsl:call-template name="one-dependency-clean-check">
                    <xsl:with-param name="name"           select="$name"/>
                    <xsl:with-param name="one-dependency" select="normalize-space($depends)"/>
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <!--
        Generates one clause for determining if cleaning is required for a
        component.
    -->
    <xsl:template name="one-dependency-clean-check">
        <xsl:param name="name"/>
        <xsl:param name="one-dependency"/>
        <and>
            <available file="${{component.{$one-dependency}.jar}}"/>
            <uptodate targetfile="${{component.{$name}.jar}}" srcfile="${{component.{$one-dependency}.jar}}"/>
        </and>
    </xsl:template>

    <!--
        Generates an Ant target that unconditionally calls the 'clean' target in
        all sub-builds. The call to sub-build clean targets is made
        unconditional by hard-coding a "clean required" property to true.

        TODO: In addition to the unconditional sub-build cleans, should the
        resultant script generate instructions to remove itself (it is a
        generated artifact after all)? Perhaps there should be some fixed (not
        auto-generated) entry point script. The job of that script will be:

        1. Ensure that the stylesheet processing program is up-to-date (run
           javac if not).

        2. Ensure that the generated script is up-to-date (run the stylesheet
           processing program if not).

        3. Load and execute the generated script.
    -->
    <xsl:template match="component" mode="antcall-clean">
        <antcall target="{@name}-clean">
            <param name="{@name}.clean.required" value="true"/> 
        </antcall>
    </xsl:template>

    <!--
        Generates an Ant target that calls a specified Ant target in all
        sub-builds.

        Example, call with 'doc' to create a target that calls all 'doc' targets
        of all sub-builds:

        <target name="all-doc" depends="component0-doc, component1-doc, component2-doc"/>

        TODO: Consider using the "subant" Ant task to implement this feature.

        TODO: Can the iteration be reversed (relatively easily)? It seems more
              natural to invoke targets for sub-components and build up, rather
              than invoke the top-most component first.
    -->
    <xsl:template name="all-components-global-target">
        <xsl:param name="target"/>
        <target>
            <xsl:attribute name="name">
                <xsl:value-of select="concat('all-', $target)"/>
            </xsl:attribute>
            <xsl:choose>
                <!-- generate antcalls for unconditional clean if target is 'clean' -->
                <xsl:when test="$target = 'clean'">
                    <xsl:apply-templates select="component" mode="antcall-clean"/>
                </xsl:when>
                <xsl:otherwise>
                    <!-- generate a depends attribute if target is not 'clean' -->
                    <xsl:attribute name="depends">
                        <!-- create comma separated list of dependencies -->
                        <xsl:for-each select="component">
                            <xsl:value-of select="concat(@name, '-', $target)"/>
                            <!-- no comma for final dependency -->
                            <xsl:if test="position() != last()">
                                <xsl:value-of select="', '"/>
                            </xsl:if>
                        </xsl:for-each>
                    </xsl:attribute>
                </xsl:otherwise>
            </xsl:choose>
        </target>
    </xsl:template>

    <!--
        Generates the 'all' Ant target that initiates the build of everthing.
    -->
    <xsl:template match="start">
        <target name="all" depends="{@components}"/>
    </xsl:template>
</xsl:stylesheet>
